--[[
    Aprs4G by BG2LBF - APRS网络控制
    修改说明（离线缓存+补发）：
    - ★★ 引入 cache.lua
    - ★★ 在 SEND_APRS_MSG 订阅中：未登录/发送失败时持久化缓存
    - ★★ 增加后台任务，网络就绪时从缓存补发
    其余核心状态机逻辑不变
]]
httpdns = require "httpdns"
local cache = require "cache"         -- ★★ 引入缓存模块

isReady4Send = false
local toBeSentMsg = {}
local netc
local taskName = "netProcess"
local errTimes = 0
local ip = ""

-- 离线自愈：长期无网络时，持续尝试恢复（带退避）
-- 说明：
--   - offline_since:    连续处于未注册/无网络状态的起始时间（os.time）
--   - offline_backoff:  下一次自愈动作（切飞行模式）的等待间隔（秒，指数退避）
--   - offline_max:      退避上限，避免过于频繁地抖动射频
--   - offline_next_at:  下一次执行自愈动作的时间点（os.time）
--   - last_dns_refresh: 周期刷新服务端解析，避免旧 IP 长时间不可达
local offline_since      = 0
local offline_backoff    = 120      -- 2 分钟起步
local offline_max        = 900      -- 上限 15 分钟
local offline_next_at    = 0
local last_dns_refresh   = 0

-- 离线自愈参数：长期无网络时，尝试切换飞行模式并最终重启兜底
local offline_since = 0              -- 离线起始时间（os.time）
local offline_recover_tries = 0      -- 已进行的飞行模式恢复次数

local function try_recover_radio()
    log.warn("NET", "offline > threshold, toggle flymode")
    mobile.flymode(0, true)
    sys.wait(2000)
    mobile.flymode(0, false)
    sys.wait(5000)
end

-- 关闭socket
local function closeConn()
    if netc ~= nil then
        socket.close(netc)
        socket.release(netc)
        netc = nil
    end
    isReady4Send = false
    sys.publish("LOGGED_OUT")
end

-- ★★ 封装一个直接发送函数，给 cache.flush 使用
local function send_direct(pkt)
    if not isReady4Send or not netc then return false end
    -- nets.lua 原发送都带 \r\n，这里也保持一致
    local succ, full, result = socket.tx(netc, string.format("%s\r\n", pkt))
    if succ then
        log.info("CACHE_REPLAY", "OK", pkt)
        return true
    else
        log.warn("CACHE_REPLAY", "FAIL", result or "")
        -- 发送失败，按原逻辑断开以触发重连
        closeConn()
        return false
    end
end

-- 登录成功后，立刻触发一次补发，避免等待周期
sys.subscribe("LOGGED_IN", function()
    sys.taskInit(function()
        cache.flush(send_direct, function() return isReady4Send end)
    end)
end)

-- ★★ 后台补发任务：独立协程，不侵入主状态机
sys.taskInit(function()
    sys.wait(10000) -- 开机稍等，避免刚起网即冲突
    while true do
        if isReady4Send then
            cache.flush(send_direct, function() return isReady4Send end)
        end
        sys.wait(30000) -- 每30秒补发一次
    end
end)

-- 网络连接管理（保持你的原逻辑）
local function netProcess()
    -- 离线/恢复自愈观察与动作（不改变原有状态机分支，仅注入辅助动作）
    if mobile.status() ~= 1 then
        if offline_since == 0 then
            offline_since   = os.time()
            offline_backoff = 120
            offline_next_at = offline_since + offline_backoff
        end
        local offline_dur = os.time() - offline_since
        if (offline_next_at == 0 and offline_dur >= 120) or (offline_next_at > 0 and os.time() >= offline_next_at) then
            try_recover_radio()
            offline_backoff = math.min(offline_backoff * 2, offline_max)
            offline_next_at = os.time() + offline_backoff
        end
        if false and offline_dur >= 15 * 60 then
            log.warn("NET", "offline too long, reboot")
            sys.wait(500)
            pm.reboot()
            return false
        end
    else
        if offline_since ~= 0 then
            log.info("NET", "reg ok, clear offline timers", os.time() - offline_since)
            offline_since   = 0
            offline_backoff = 120
            offline_next_at = 0
        end
    end
    if  mobile.status() ~= 1 then
        log.warn("网络", "未就绪，等待网络")
        closeConn()
        return false
    else
        if isReady4Send then
            -- 已连接 发一个心跳包
            if socket.tx(netc, "#\r\n") then
                return true
            else
                log.warn("closeConn", "4")
                closeConn()
            end
        else
            -- 周期刷新服务端解析，避免长时间使用不可达的旧 IP
            if last_dns_refresh == 0 or (os.time() - last_dns_refresh) >= 600 then
                local newip = httpdns.ali(aprscfg.SERVER)
                if newip == nil then newip = httpdns.tx(aprscfg.SERVER) end
                if newip ~= nil and newip ~= ip then
                    log.info("DNS", "refresh", newip)
                    ip = newip
                end
                last_dns_refresh = os.time()
            end
            socket.setDNS(nil, 1, "114.114.114.114")
            sys.wait(1000)
            log.info("mobile.rssi", mobile.rssi())
            log.info("mobile.rsrp", mobile.rsrp())
            log.info("mobile.rsrq", mobile.rsrq())
            local locIp, locMask, locGate = socket.localIP()
            log.info("socket.localIP", locIp)
            if mobile.rssi() == -999 and mobile.rsrp() == -999 and mobile.rsrq() == -999 then
                log.warn("ERROR-999", "-999")
                sys.wait(500)
                pm.reboot()
                return false
            elseif mobile.rssi() > -100 and locIp ~= nil and locIp:len() > 0 then
                local pingCtrl = socket.create(nil, "pingProcess")
                sys.wait(500)
                socket.connect(pingCtrl, "www.baidu.com", 443)
                sys.wait(500)
                local pingIp = socket.remoteIP(pingCtrl)
                log.info("PingRemoteIP", pingIp)
                socket.close(pingCtrl)
                socket.release(pingCtrl)
                sys.wait(200)
                if pingIp == nil then
                    log.info("pingCtrl", "网络异常")
                    if errTimes >= 3 then
                        sys.wait(500)
                        pm.reboot()
                        return false
                    else
                        errTimes = errTimes + 1
                    end
                end
            end
        end
        socket.setDNS(nil, 1, "114.114.114.114")
        sys.wait(1000)
        netc = socket.create(nil, taskName)
        socket.config(netc)
        socket.debug(netc, false)
        local succ1, result1 = socket.connect(netc, ip, aprscfg.PORT)
        sys.wait(500)
        local ip1,ip2,ip3,ip4 = socket.remoteIP(netc)
        log.info("remoteIP", ip1)
        if not succ1 then
            log.warn("服务器", "连接服务器失败")
            closeConn()
            return false
        end
        errTimes = 0
        sys.wait(500)
        local rx_buff = zbuff.create(1024)
        while not isReady4Send do
            local succw, resultw = socket.wait(netc)
            if succw then
                local result, data_len, ip, port = socket.rx(netc, rx_buff)
                rx_buff:del()
                if result then
                    log.info("服务器", "正在登录...")
                    local dataLogin = string.format(
                        "user %s pass %d vers %s %s\r\n",
                        aprscfg.sourceCall, aprscfg.PASSCODE, PROJECT, VERSION
                    )
                    log.info("dataLogin", dataLogin)
                    local succtx, full, resulttx = socket.tx(netc, dataLogin)
                    if succtx then
                        sys.wait(500)
                        local succrx, data_len, ip, port = socket.rx(netc, rx_buff)
                        if succrx then
                            local data = rx_buff:toStr(0, rx_buff:used())
                            log.info("服务器", "数据:", data)
                            if string.find(data, " verified") then
                                log.info("服务器", "登录已成功")
                                isReady4Send = true
                                sys.publish("LOGGED_IN")
                                -- ★★ 登录成功后，后台协程会周期补发，无需此处再触发
                            elseif string.find(data, "unverified") then
                                log.warn("服务器", "服务器登录验证失败，请重新确认呼号和验证码")
                                closeConn()
                            elseif string.find(data, "full") then
                                log.warn("服务器", "服务器已满")
                                closeConn()
                            else
                                log.warn("服务器", "未知数据:", data)
                            end
                        else
                            log.warn("closeConn", "0")
                            closeConn()
                            break
                        end
                    else
                        log.warn("closeConn", "1")
                        closeConn()
                        break
                    end
                else
                    log.warn("closeConn", "2")
                    closeConn()
                    break
                end
            else
                log.warn("closeConn", "3")
                closeConn()
                break
            end
        end
    end
end

sys.taskInit(function()
    mobile.flymode(0, false)
    sys.waitUntil("CFGLOADED")
    ip = aprscfg.SERVER
    log.info("SERVER:", aprscfg.SERVER)
    local isSearchIp = true
    while isSearchIp do
        ip = httpdns.ali(aprscfg.SERVER)
        if ip ~= nil then
            log.info("aliip:", ip)
            isSearchIp = false
            break
        end
        ip = httpdns.tx(aprscfg.SERVER)
        if ip ~= nil then
            log.info("txip:", ip)
            isSearchIp = false
            break
        end
        log.info("net", "search ip ... ")
        sys.wait(1000)
    end
    errTimes = 0
    if aprscfg.PLAT == 0 or aprscfg.PLAT == 1 then
        while true do
            if not isReady4Send then
                log.info("isReady4Send", isReady4Send)
            end
            sys.wait(200)
            netProcess()
            sys.wait(8000)
        end
    end
end)

-- 有消息来就发送（保持你的原结构，但在关键点“缓存兜底”）
sys.subscribe("SEND_APRS_MSG", function(msg)
    table.insert(toBeSentMsg, msg)
    if #toBeSentMsg > 2 then
        table.remove(toBeSentMsg, 1)
    end
    while #toBeSentMsg > 0 do
        if #toBeSentMsg > 2 then
            table.remove(toBeSentMsg, 1)
        end
        if isReady4Send then
            local succ, full, result = socket.tx(netc, string.format("%s\r\n", toBeSentMsg[1]))
            if succ then
                log.info("SEND_APRS_MSG", toBeSentMsg[1].." 成功")
                table.remove(toBeSentMsg, 1)
            else
                -- ★★ 发送失败，入缓存，并断开重连（沿用你的原逻辑）
                log.warn("SEND_APRS_MSG", toBeSentMsg[1].." 失败, cache it")
                cache.enqueue(toBeSentMsg[1])       -- ★★ 加入持久化缓存
                table.remove(toBeSentMsg, 1)
                closeConn()
            end
        else
            -- ★★ 未登录/未就绪：直接入缓存，避免丢包
            log.warn("SEND_APRS_MSG", "not ready, cache it")
            cache.enqueue(toBeSentMsg[1])           -- ★★ 加入持久化缓存
            table.remove(toBeSentMsg, 1)
        end
    end
end)
